In your final repo, there should be an R markdown file that organizes all computational steps for evaluating your proposed Facial Expression Recognition framework.

This file is currently a template for running evaluation experiments. You should update it according to your codes but following precisely the same structure.

rr if(!require()){ install.packages() BiocManager::install() } if(!require(.matlab)){ install.packages(.matlab) } if(!require()){ install.packages() } if(!require()){ install.packages() } if(!require()){ install.packages() } if(!require(2)){ install.packages(2) } if(!require()){ install.packages() } if(!require()){ install.packages() } if(!require()){ install.packages() } if(!require(1071)){ install.packages(1071) } if(!require()){ install.packages() } if(!require()){ install.packages() } library(R.matlab) library(readxl) library(dplyr) library(EBImage) library(ggplot2) library(caret) library(glmnet) library(WeightedROC) library(e1071) library(xgboost) library(randomForest)

Step 0 set work directories

set.seed(2020)
# setwd("~/Project3-FacialEmotionRecognition/doc")
# here replace it with your own path or manually set it in RStudio to where this rmd file is located. 
# use relative path for reproducibility

Provide directories for training images. Training images and Training fiducial points will be in different subfolders.

rr train_dir <- ../data/train_set/ # This will be modified for different data sets. train_image_dir <- paste(train_dir, /, sep=\) train_pt_dir <- paste(train_dir, /, sep=\) train_label_path <- paste(train_dir, .csv, sep=\)

Step 1: set up controls for evaluation experiments.

In this chunk, we have a set of controls for the evaluation experiments.

rr run.cv <- FALSE # run cross-validation on the training set run.cv.baseline <- FALSE # run cross-validation on the gbm baseline sample.reweight <- TRUE # run sample reweighting in model training K <- 5 # number of CV folds run.feature.train <- TRUE # process features for training set run.test <- TRUE # run evaluation on an independent test set run.feature.test <- TRUE # process features for test set run.cv.svm <- FALSE run.test.svm <- TRUE

Using cross-validation or independent test set evaluation, we compare the performance of models with different specifications. In this Starter Code, we tune parameter lambda (the amount of shrinkage) for logistic regression with LASSO penalty.

rr lmbd = c(1e-3, 5e-3, 1e-2, 5e-2, 1e-1) model_labels = paste(Penalty with lambda =, lmbd) #gbm parameters tuning: n.trees = c(10,50,100,200) shrinkage = c(0.01,0.05,0.1,0.15) #svm parameters tuning: cost = c(0.0000001,0.000001,0.00001,0.0001,0.001,0.01,1) model_labels_svm = paste(with cost =, cost) #xgboost parameters tuning params <- list(booster = , objective = :logistic, eta=0.3, gamma=0, max_depth=6, min_child_weight=1, subsample=1, colsample_bytree=1) #random forest parameters tuning ntrees <- 128 #according to a paper by Thais Mayumi Oshiro, Pedro Santoro Perez, and Jos ́e Augusto Baranauska, # the AUC gain for increasing number of trees is minimal after 128, # after observing a multitude of their datasets

Step 2: import data and train-test split

rr #train-test split info <- read.csv(train_label_path) n <- nrow(info) n_train <- round(n*(4/5), 0) train_idx <- sample(info\(Index, n_train, replace = F) test_idx <- setdiff(info\)Index, train_idx)

If you choose to extract features from images, such as using Gabor filter, R memory will exhaust all images are read together. The solution is to repeat reading a smaller batch(e.g 100) and process them.

rr n_files <- length(list.files(train_image_dir)) image_list <- list() for(i in 1:100){ image_list[[i]] <- readImage(paste0(train_image_dir, sprintf(%04d, i), .jpg)) }

Fiducial points are stored in matlab format. In this step, we read them and store them in a list.

rr #function to read fiducial points #input: index #output: matrix of fiducial points corresponding to the index readMat.matrix <- function(index){ return(round(readMat(paste0(train_pt_dir, sprintf(%04d, index), .mat))[[1]],0)) } #load fiducial points fiducial_pt_list <- lapply(1:n_files, readMat.matrix) save(fiducial_pt_list, file=../output/fiducial_pt_list.RData)

Step 3: construct features and responses

Figure1

Figure1

feature.R should be the wrapper for all your feature engineering functions and options. The function feature( ) should have options that correspond to different scenarios for your project and produces an R object that contains features and responses that are required by all the models you are going to evaluate later.

rr source(../lib/feature.R) tm_feature_train <- NA if(run.feature.train){ tm_feature_train <- system.time(dat_train <- feature(fiducial_pt_list, train_idx)) save(dat_train, file=../output/feature_train.RData) }else{ load(file=../output/feature_train.RData) } tm_feature_test <- NA if(run.feature.test){ tm_feature_test <- system.time(dat_test <- feature(fiducial_pt_list, test_idx)) save(dat_test, file=../output/feature_test.RData) }else{ load(file=../output/feature_test.RData) }

Step 4: Train a classification model with training features and responses

Call the train model and test model from library.

train.R and test.R should be wrappers for all your model training steps and your classification/prediction steps.

rr source(../lib/train.R) source(../lib/test.R) source(../lib/train_gbm.R) source(../lib/test_gbm.R) source(../lib/train_SVM.R) source(../lib/test_SVM.R) source(../lib/fit_train_xgboost.R) source(../lib/fit_train_randomforest.R)

Model selection with cross-validation

  • Do model selection by choosing among different values of training model parameters.

Baseline Model * Baseline/GBM

rr feature_train = as.matrix(dat_train[, -6007]) label_train = as.integer(dat_train$label)

source(../lib/cross_validation.R) source(../lib/cross_validation_SVM.R) source(../lib/cv_gbm.R)

if(run.cv.baseline){

mean_error_cv <- matrix(0, nrow = length(n.trees), ncol = length(shrinkage)) sd_error_cv <- matrix(0, nrow = length(n.trees), ncol = length(shrinkage)) mean_auc_cv <- matrix(0, nrow = length(n.trees), ncol = length(shrinkage)) sd_auc_cv <- matrix(0, nrow = length(n.trees), ncol = length(shrinkage))

for(i in 1:length(n.trees)){ cat(.trees =, n.trees[i],\n) for(k in 1:length(shrinkage)){ cat(=, shrinkage[k],\n)

res_cv_gbm <- cv_gbm(features = feature_train, labels = label_train, K, n.trees = n.trees[i],shrinkage = shrinkage[k],reweight = sample.reweight)

mean_error_cv[i,k]<-res_cv_gbm[1]
 sd_error_cv[i,k]<-res_cv_gbm[2]
  mean_auc_cv[i,k]<-res_cv_gbm[3]
   sd_auc_cv[i,k]<-res_cv_gbm[4]
  
save(mean_error_cv, file=\../output/mean_error_cv.RData\)
save(sd_error_cv, file=\../output/sd_error_cv.RData\)
save(mean_auc_cv, file=\../output/mean_auc_cv.RData\)
save(sd_auc_cv, file=\../output/sd_auc_cv.RData\) 
}}

} else{ load(../output/mean_error_cv.RData) load(../output/sd_error_cv.RData)
load(../output/mean_auc_cv.RData) load(../output/sd_auc_cv.RData) }

if(run.cv){ res_cv <- matrix(0, nrow = length(lmbd), ncol = 4) for(i in 1:length(lmbd)){ cat(= , lmbd[i], \n) res_cv[i,] <- cv.function(features = feature_train, labels = label_train, K, l = lmbd[i], reweight = sample.reweight) save(res_cv, file=../output/res_cv.RData) } }else{ load(../output/res_cv.RData) }

  • SVM

rr if(run.cv.svm){ res_cv_svm <- matrix(0, nrow = length(cost), ncol = 4) for(i in 1:length(cost)){ cat(= , cost[i], \n) res_cv_svm[i,] <- svm_cv(features = feature_train, labels = label_train, K, cost=cost[i], reweight = sample.reweight) save(res_cv_svm, file=../output/res_cv_svm.RData) } }else{ load(../output/res_cv_svm.RData) }

Improved Model * XGBoost

rr source(../lib/xgboost_cv.R) feature_train = as.matrix(dat_train[, -6007]) label_train = as.integer(dat_train$label) label_train_xgb <- label_train label_train_xgb[label_train_xgb == 2] <- 0 set_rounds <- 50 K <- 5 new_params <- cv_xgboost(params, feature_train, label_train_xgb, set_rounds, K)

[1] train-error:0.102605+0.007682   test-error:0.221244+0.013096 
[11]    train-error:0.005313+0.001335   test-error:0.178322+0.017084 
[21]    train-error:0.000000+0.000000   test-error:0.173332+0.013634 
[31]    train-error:0.000000+0.000000   test-error:0.169574+0.016910 
[41]    train-error:0.000000+0.000000   test-error:0.171246+0.015282 
[50]    train-error:0.000000+0.000000   test-error:0.169996+0.013158 

rr library(tidyr)

df_mean_error=data.frame(mean_error_cv)%>% setNames(shrinkage)%>% mutate(n.trees=n.trees)%>% gather(shrinkage,mean_error,0.01:0.15)

df_sd_error=data.frame(sd_error_cv)%>% setNames(shrinkage)%>% mutate(n.trees=n.trees)%>% gather(shrinkage,sd_error,0.01:0.15)

df_mean_auc=data.frame(mean_auc_cv)%>% setNames(shrinkage)%>% mutate(n.trees=n.trees)%>% gather(shrinkage,mean_auc,0.01:0.15)

df_sd_auc=data.frame(sd_auc_cv)%>% setNames(shrinkage)%>% mutate(n.trees=n.trees)%>% gather(shrinkage,sd_auc,0.01:0.15)

res_cv_gbm <- df_mean_error%>%mutate(sd_error=df_sd_error\(sd_error, mean_auc=df_mean_auc\)mean_auc, sd_auc=df_sd_auc$sd_auc) save(res_cv_gbm,file = ../output/res_cv_gbm.RData)

Visualize cross-validation results.

rr load(../output/res_cv_gbm.RData)

if(run.cv.baseline){ p1 <- res_cv_gbm %>% ggplot(aes(x = n.trees, y = mean_error, ymin = mean_error - sd_error, ymax = mean_error +sd_error)) + geom_crossbar() + facet_wrap(~shrinkage) + theme(axis.text.x = element_text(angle = 90, hjust = 1))

p2 <- res_cv_gbm %>% ggplot(aes(x = n.trees, y = mean_auc, ymin = mean_auc - sd_auc, ymax = mean_auc + sd_auc)) + facet_wrap(~shrinkage) + geom_crossbar() + theme(axis.text.x = element_text(angle = 90, hjust = 1))

print(p1) print(p2) }

res_cv_svm <- as.data.frame(res_cv_svm) colnames(res_cv_svm) <- c(_error, _error, _AUC, _AUC) res_cv_svm$k = as.factor(cost) run.cv.svm <- TRUE if(run.cv.svm){ p1 <- res_cv_svm %>% ggplot(aes(x = as.factor(cost), y = mean_error, ymin = mean_error - sd_error, ymax = mean_error + sd_error)) + geom_crossbar() + theme(axis.text.x = element_text(angle = 90, hjust = 1))

p2 <- res_cv_svm %>% ggplot(aes(x = as.factor(cost), y = mean_AUC, ymin = mean_AUC - sd_AUC, ymax = mean_AUC + sd_AUC)) + geom_crossbar() + theme(axis.text.x = element_text(angle = 90, hjust = 1))

print(p1) print(p2) }

  • Choose the "best" parameter value for baseline model

rr best_n.trees = as.numeric(res_cv_gbm[which.min(res_cv_gbm$mean_error),1]) best_shrinkage = as.numeric(res_cv_gbm[which.min(res_cv_gbm$mean_error),2])

best_cost <- cost[which.min(res_cv_svm$mean_error)]

  • Choose "best" number of trees and mtry for random forest model

rr source(../lib/rf_param_choice.R) feature_train = as.matrix(dat_train[, -6007]) label_train = as.integer(dat_train$label) init_mtry <- sqrt(ncol(dat_train)) rf_opt_tree <- param_choice_rf(feature_train = feature_train, label_train = label_train, mtry = init_mtry, ntree = ntrees)

Train different models

  • Train the baseline model with the entire training set using the selected model (model parameter) via cross-validation.

rr # training weights weight_train <- rep(NA, length(label_train)) for (v in unique(label_train)){ weight_train[label_train == v] = 0.5 * length(label_train) / length(label_train[label_train == v]) } if (sample.reweight){ tm_train_baseline <- system.time(fit_train_baseline <- train_gbm(feature_train, label_train, w = weight_train,best_n.trees, best_shrinkage)) } else { tm_train_baseline <- system.time(fit_train_baseline <- train_gbm(feature_train, label_train, w = NULL, best_n.trees, best_shrinkage)) } save(fit_train_baseline, file=../output/fit_train_baseline.RData)

  • Train the SVM model with the entire training set using the selected model (model parameter) via cross-validation.

rr weight_train <- rep(NA, length(label_train)) for (v in unique(label_train)){ weight_train[label_train == v] = 0.5 * length(label_train) / length(label_train[label_train == v]) } if (sample.reweight){ tm_train_svm <- system.time(fit_train_svm <-svm_train(feature_train, label_train, w = weight_train, best_cost)) } else { tm_train_svm <- system.time(fit_train_svm <-svm_train(feature_train, label_train, w = NULL, best_cost)) } save(fit_train_svm, file=../output/fit_train_svm.RData)

  • Train the XGBoost model with optimal parameters

rr xgb_train_time <- system.time(fit_train_xgb <- xgboost_train(features = feature_train, labels = label_train_xgb, params = new_params, rounds = set_rounds))

[22:46:58] WARNING: amalgamation/../src/learner.cc:541: 
Parameters: { early_stop_round, silent } might not be used.

  This may not be accurate due to some parameters are only used in language bindings but
  passed down to XGBoost core.  Or some parameters are not used but slip through this
  verification. Please open an issue if you find above cases.


[1] train-error:0.116250 
[2] train-error:0.091250 
[3] train-error:0.080833 
[4] train-error:0.070417 
[5] train-error:0.059167 
[6] train-error:0.050000 
[7] train-error:0.040000 
[8] train-error:0.031667 
[9] train-error:0.025833 
[10]    train-error:0.020417 
[11]    train-error:0.015000 
[12]    train-error:0.011667 
[13]    train-error:0.007500 
[14]    train-error:0.003750 
[15]    train-error:0.002500 
[16]    train-error:0.001667 
[17]    train-error:0.001667 
[18]    train-error:0.000417 
[19]    train-error:0.000000 
[20]    train-error:0.000000 
[21]    train-error:0.000000 
[22]    train-error:0.000000 
[23]    train-error:0.000000 
[24]    train-error:0.000000 
[25]    train-error:0.000000 
[26]    train-error:0.000000 
[27]    train-error:0.000000 
[28]    train-error:0.000000 
[29]    train-error:0.000000 
[30]    train-error:0.000000 
[31]    train-error:0.000000 
[32]    train-error:0.000000 
[33]    train-error:0.000000 
[34]    train-error:0.000000 
[35]    train-error:0.000000 
[36]    train-error:0.000000 
[37]    train-error:0.000000 
[38]    train-error:0.000000 
[39]    train-error:0.000000 
[40]    train-error:0.000000 
[41]    train-error:0.000000 
[42]    train-error:0.000000 
[43]    train-error:0.000000 
[44]    train-error:0.000000 
[45]    train-error:0.000000 
[46]    train-error:0.000000 
[47]    train-error:0.000000 
[48]    train-error:0.000000 
[49]    train-error:0.000000 
[50]    train-error:0.000000 

rr save(fit_train_xgb, file=../output/fit_train_xgb.RData)

  • Train the Random Forest model with optimal parameters

rr load(file = ../output/res_oob_rf.RData) rf_train_time <- system.time(fit_train_randomforest <- rf_train(feature_train = feature_train, label_train = label_train, mtry = rf_opt_tree\(mtry, ntree = rf_opt_tree\)ntree)) save(fit_train_rf, file=../output/fit_train_rf.RData)

Error in save(fit_train_rf, file = \../output/fit_train_rf.RData\) : 
  目标对象‘fit_train_rf’不存在
  • train PCA + LDA

rr #PCA for training features source(../lib/train_PCA.R) # make dat_train a numeric data frame dat_train.new <- matrix(0, ncol = ncol(dat_train) - 1, nrow = nrow(dat_train)) for (i in 1:(ncol(dat_train) - 1)) { dat_train.new[,i] <- as.numeric(dat_train[[i]]) } dat_train.new <- as.data.frame(dat_train.new) #PCA for training features tm_train_pca <- system.time(fit_train_pca <- train_pca(dat_train.new)) save(fit_train_pca, file=../output/pca_train.RData) # determine the important principle components screeplot(fit_train_pca, type = )

rr # The proportion of variance for first 300 PCs sum((fit_train_pca\(sdev[1:300])^2) / sum((fit_train_pca\)sdev)^2)

[1] 0.998993

Therefore, we can choose the first 300 principle components because they explain most of the total variance, which is around 99.9%.

rr # get the features of principle components with emotion index dat_train_pca <- data.frame(fit_train_pca$x[,1:300], emotion_idx = dat_train[,6007])

Use trained PCA model to test data

rr source(../lib/test_PCA.R) dat_test.new <- dat_test colnames(dat_test.new) <- c(colnames(dat_train.new), _idx) tm_test_pca <- system.time(dat_test.new <- test_pca(fit_train_pca, dat_test.new)) #features of testing principle components with the emotion index dat_test_pca <- data.frame(dat_test.new[,1:300], emotion_idx = dat_test[,6007]) save(dat_train_pca, file=../output/feature_train_pca.RData) save(dat_test_pca, file=../output/feature_test_pca.RData)

Apply LDA model

rr #load(../output/feature_train_pca.RData) #train LDA model source(../lib/train_LDA.R) tm_train_lda <- system.time(fit_train_lda <- train_lda(dat_train_pca))


载入程辑包:‘MASS’

The following object is masked from ‘package:dplyr’:

    select

rr save(fit_train_lda, file=../output/LDA_train.RData) #training accuracy in LDA model source(../lib/test_LDA.R) pred_train_lda <- test_lda(fit_train_lda, dat_train_pca) accu_train_lda <- mean(pred_train_lda == dat_train_pca$emotion_idx) #cat(trainig accuracy of model LDA, , accu_train_lda*100, %.) #cat(for training model LDA = , tm_train_lda[1], )

Step 5: Run test on test images

*Baseline model

rr tm_test_baseline = NA feature_test <- as.matrix(dat_test[, -6007]) if(run.test){ load(file=../output/fit_train_baseline.RData) tm_test_baseline <- system.time({label_pred_baseline <- as.integer(test_gbm(fit_train_baseline,feature_test,best_n.trees, best_shrinkage, pred.type = 'link')); prob_pred_baseline <- test_gbm(fit_train_baseline, feature_test,best_n.trees, best_shrinkage, pred.type = 'response')}) }

*SVM model

rr tm_test = NA feature_test <- as.matrix(dat_test[, -6007]) if(run.test.svm){ load(file=../output/fit_train_svm.RData) tm_test_svm <- system.time({ label_pred_svm <- as.integer(svm_test(fit_train_svm, feature_test, pred.type = 'class')); prob_pred_svm <- svm_test(fit_train_svm, feature_test, pred.type = 'response')}) }

*XGBoost

rr tm_test_xgb = NA feature_test <- as.matrix(dat_test[, -6007]) if(run.test){ load(file=../output/fit_train_xgb.RData) tm_test_xgb <- system.time({label_pred_xgb <- predict(fit_train_xgb, feature_test, pred.type = 'class'); label_pred_xgb[label_pred_xgb >= 0.5] <- 1; label_pred_xgb[label_pred_xgb < 0.5] <- 0; prob_pred_xgb <- predict(fit_train_xgb, feature_test, pred.type = 'response')}) }

*Random Forest

rr tm_test_rf = NA feature_test <- as.matrix(dat_test[, -6007]) if(run.test){ load(file=../output/fit_train_rf.RData) tm_test_rf <- system.time({label_pred_rf <- predict(fit_train_randomforest, feature_test, pred.type = 'class'); prob_pred_rf <- predict(fit_train_randomforest, feature_test, type = 'prob')}) }

*LDA

rr source(../lib/test_LDA.R) #load(../output/feature_test_pca.RData) load(file=../output/LDA_train.RData) tm_test_lda <- system.time(pred_lda <- test_lda(fit_train_lda, dat_test_pca))

Evaluation

rr ## reweight the test data to represent a balanced label distribution label_test <- as.integer(dat_test$label) weight_test <- rep(NA, length(label_test)) for (v in unique(label_test)){ weight_test[label_test == v] = 0.5 * length(label_test) / length(label_test[label_test == v]) }

accu_baseline <- mean(label_pred_baseline == label_test) tpr.fpr.baseline <- WeightedROC(prob_pred_baseline, label_test, weight_test) auc_baseline <- WeightedAUC(tpr.fpr.baseline)

cat(accuracy of model GBM: with n.trees=,best_n.trees,shrinkage =, best_shrinkage, , accu_baseline*100, %.) cat(AUC of model GBM: with n.trees=, best_n.trees,shrinkage =, best_shrinkage, , auc_baseline, .)

rr label_test <- as.integer(dat_test$label) weight_test <- rep(NA, length(label_test)) for (v in unique(label_test)){ weight_test[label_test == v] = 0.5 * length(label_test) / length(label_test[label_test == v]) }

accu_svm <- mean(label_pred_svm == label_test) tpr.fpr.svm <- WeightedROC(prob_pred_svm, label_test, weight_test) auc_svm <- WeightedAUC(tpr.fpr.svm) cat(accuracy of model:, model_labels_svm[which.min(res_cv_svm$mean_error)], , accu_svm*100, %.) cat(AUC of model:, model_labels_svm[which.min(res_cv_svm$mean_error)], , auc_svm, .)

rr label_test <- as.integer(dat_test$label) label_test_xgb <- label_test label_test_xgb[label_test_xgb==2] = 0 accu_xgb <- mean((label_pred_xgb == label_test_xgb)) tpr.fpr_xgb <- WeightedROC(prob_pred_xgb, label_test_xgb) auc_xgb <- WeightedAUC(tpr.fpr_xgb)

cat(accuracy of the XGBoost model:, , accu_xgb*100, %.) cat(AUC of the XGBoost model:, , auc_xgb, .)

rr label_test <- as.integer(dat_test$label) accu_rf <- mean(label_pred_rf == label_test) tpr.fpr.rf <- WeightedROC(prob_pred_rf[,2], label_test) auc_rf <- WeightedAUC(tpr.fpr.rf)

cat(accuracy of the Random Forest model:, , accu_rf*100, %.) cat(AUC of the Random Forest model:, , auc_rf, .)

rr accu_lda <- mean(dat_test_pca\(emotion_idx == pred_lda) label_lda <- as.numeric(dat_test_pca\)emotion_idx) tpr.fpr.lda <- WeightedROC(as.numeric(pred_lda), label_lda) auc_lda <- WeightedAUC(tpr.fpr.lda) cat(accuracy of the PCA+LDA model:, , accu_lda*100, %.)

The accuracy of the PCA+LDA model: is 82.33333 %.

rr cat(AUC of the PCA+LDA model:, , auc_lda, .)

The AUC of the PCA+LDA model: is 0.6773504 .

Summarize Running Time

Prediction performance matters, so does the running times for constructing features and for training the model, especially when the computation resource is limited.

rr cat(for constructing training features=, tm_feature_train[1], ) cat(for constructing testing features=, tm_feature_test[1], ) cat(for training model=, tm_train_baseline[1], ) cat(for testing model=, tm_test_baseline[1], )

rr cat(for constructing training features=, tm_feature_train[1], ) cat(for constructing testing features=, tm_feature_test[1], ) cat(for training model=, tm_train_svm[1], ) cat(for testing model=, tm_test_svm[1], )

rr cat(for constructing training features=, tm_feature_train[1], ) cat(for constructing testing features=, tm_feature_test[1], ) cat(for training model=, xgb_train_time[1], ) cat(for testing model=, tm_test_xgb[1], )

rr cat(for constructing training features=, tm_feature_train[1], ) cat(for constructing testing features=, tm_feature_test[1], ) cat(for training random forest model=, rf_train_time[1], ) cat(for testing random forest model=, tm_test_rf[1], )

rr cat(for constructing training features=, tm_feature_train[1], )

Time for constructing training features= 1.402 s 

rr cat(for constructing testing features=, tm_feature_test[1], )

Time for constructing testing features= 0.3 s 

rr cat(for training PCA =, tm_train_pca[1], )

Time for training PCA = 325.249 s 

rr cat(for testing PCA =, tm_test_pca[1], )

Time for testing PCA = 8.124 s 

rr cat(for training model LDA = , tm_train_lda[1], )

Time for training model LDA =  1.927 s 

rr cat(for testing model LDA = ,tm_test_lda[1], )

Time for testing model LDA =  0.033 s 

Reference

LS0tCnRpdGxlOiAiTWFpbiIKYXV0aG9yOiAiRGFpenkgTGFtLCBQZXRlciBLd2F1aywgUWl6aGVuIFlhbmcsIEVsbGVuIENoZW4sIERhcnlsIEtvdyIKb3V0cHV0OgogIHBkZl9kb2N1bWVudDogZGVmYXVsdAogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKLS0tCgpJbiB5b3VyIGZpbmFsIHJlcG8sIHRoZXJlIHNob3VsZCBiZSBhbiBSIG1hcmtkb3duIGZpbGUgdGhhdCBvcmdhbml6ZXMgKiphbGwgY29tcHV0YXRpb25hbCBzdGVwcyoqIGZvciBldmFsdWF0aW5nIHlvdXIgcHJvcG9zZWQgRmFjaWFsIEV4cHJlc3Npb24gUmVjb2duaXRpb24gZnJhbWV3b3JrLiAKClRoaXMgZmlsZSBpcyBjdXJyZW50bHkgYSB0ZW1wbGF0ZSBmb3IgcnVubmluZyBldmFsdWF0aW9uIGV4cGVyaW1lbnRzLiBZb3Ugc2hvdWxkIHVwZGF0ZSBpdCBhY2NvcmRpbmcgdG8geW91ciBjb2RlcyBidXQgZm9sbG93aW5nIHByZWNpc2VseSB0aGUgc2FtZSBzdHJ1Y3R1cmUuIAoKYGBge3IgbWVzc2FnZT1GQUxTRX0KaWYoIXJlcXVpcmUoIkVCSW1hZ2UiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiQmlvY01hbmFnZXIiKQogIEJpb2NNYW5hZ2VyOjppbnN0YWxsKCJFQkltYWdlIikKfQppZighcmVxdWlyZSgiUi5tYXRsYWIiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiUi5tYXRsYWIiKQp9CmlmKCFyZXF1aXJlKCJyZWFkeGwiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygicmVhZHhsIikKfQoKaWYoIXJlcXVpcmUoImRwbHlyIikpewogIGluc3RhbGwucGFja2FnZXMoImRwbHlyIikKfQppZighcmVxdWlyZSgicmVhZHhsIikpewogIGluc3RhbGwucGFja2FnZXMoInJlYWR4bCIpCn0KCmlmKCFyZXF1aXJlKCJnZ3Bsb3QyIikpewogIGluc3RhbGwucGFja2FnZXMoImdncGxvdDIiKQp9CgppZighcmVxdWlyZSgiY2FyZXQiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiY2FyZXQiKQp9CgppZighcmVxdWlyZSgiZ2xtbmV0IikpewogIGluc3RhbGwucGFja2FnZXMoImdsbW5ldCIpCn0KCmlmKCFyZXF1aXJlKCJXZWlnaHRlZFJPQyIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJXZWlnaHRlZFJPQyIpCn0KCmlmKCFyZXF1aXJlKCJlMTA3MSIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJlMTA3MSIpCn0KCmlmKCFyZXF1aXJlKCJ4Z2Jvb3N0IikpewogIGluc3RhbGwucGFja2FnZXMoInhnYm9vc3QiKQp9CgppZighcmVxdWlyZSgicmFuZG9tRm9yZXN0IikpewogIGluc3RhbGwucGFja2FnZXMoInJhbmRvbUZvcmVzdCIpCn0KCmxpYnJhcnkoUi5tYXRsYWIpCmxpYnJhcnkocmVhZHhsKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KEVCSW1hZ2UpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShjYXJldCkKbGlicmFyeShnbG1uZXQpCmxpYnJhcnkoV2VpZ2h0ZWRST0MpCmxpYnJhcnkoZTEwNzEpCmxpYnJhcnkoeGdib29zdCkKbGlicmFyeShyYW5kb21Gb3Jlc3QpCmBgYAoKIyMjIFN0ZXAgMCBzZXQgd29yayBkaXJlY3RvcmllcwpgYGB7ciB3a2RpciwgZXZhbD1GQUxTRX0Kc2V0LnNlZWQoMjAyMCkKIyBzZXR3ZCgifi9Qcm9qZWN0My1GYWNpYWxFbW90aW9uUmVjb2duaXRpb24vZG9jIikKIyBoZXJlIHJlcGxhY2UgaXQgd2l0aCB5b3VyIG93biBwYXRoIG9yIG1hbnVhbGx5IHNldCBpdCBpbiBSU3R1ZGlvIHRvIHdoZXJlIHRoaXMgcm1kIGZpbGUgaXMgbG9jYXRlZC4gCiMgdXNlIHJlbGF0aXZlIHBhdGggZm9yIHJlcHJvZHVjaWJpbGl0eQpgYGAKClByb3ZpZGUgZGlyZWN0b3JpZXMgZm9yIHRyYWluaW5nIGltYWdlcy4gVHJhaW5pbmcgaW1hZ2VzIGFuZCBUcmFpbmluZyBmaWR1Y2lhbCBwb2ludHMgd2lsbCBiZSBpbiBkaWZmZXJlbnQgc3ViZm9sZGVycy4gCmBgYHtyfQp0cmFpbl9kaXIgPC0gIi4uL2RhdGEvdHJhaW5fc2V0LyIgIyBUaGlzIHdpbGwgYmUgbW9kaWZpZWQgZm9yIGRpZmZlcmVudCBkYXRhIHNldHMuCnRyYWluX2ltYWdlX2RpciA8LSBwYXN0ZSh0cmFpbl9kaXIsICJpbWFnZXMvIiwgc2VwPSIiKQp0cmFpbl9wdF9kaXIgPC0gcGFzdGUodHJhaW5fZGlyLCAgInBvaW50cy8iLCBzZXA9IiIpCnRyYWluX2xhYmVsX3BhdGggPC0gcGFzdGUodHJhaW5fZGlyLCAibGFiZWwuY3N2Iiwgc2VwPSIiKSAKYGBgCgojIyMgU3RlcCAxOiBzZXQgdXAgY29udHJvbHMgZm9yIGV2YWx1YXRpb24gZXhwZXJpbWVudHMuCgpJbiB0aGlzIGNodW5rLCB3ZSBoYXZlIGEgc2V0IG9mIGNvbnRyb2xzIGZvciB0aGUgZXZhbHVhdGlvbiBleHBlcmltZW50cy4gCgorIChUL0YpIGNyb3NzLXZhbGlkYXRpb24gb24gdGhlIHRyYWluaW5nIHNldAorIChUL0YpIHJld2VpZ2h0aW5nIHRoZSBzYW1wbGVzIGZvciB0cmFpbmluZyBzZXQgCisgKG51bWJlcikgSywgdGhlIG51bWJlciBvZiBDViBmb2xkcworIChUL0YpIHByb2Nlc3MgZmVhdHVyZXMgZm9yIHRyYWluaW5nIHNldAorIChUL0YpIHJ1biBldmFsdWF0aW9uIG9uIGFuIGluZGVwZW5kZW50IHRlc3Qgc2V0CisgKFQvRikgcHJvY2VzcyBmZWF0dXJlcyBmb3IgdGVzdCBzZXQKCmBgYHtyIGV4cF9zZXR1cH0KcnVuLmN2IDwtIEZBTFNFICMgcnVuIGNyb3NzLXZhbGlkYXRpb24gb24gdGhlIHRyYWluaW5nIHNldApydW4uY3YuYmFzZWxpbmUgPC0gRkFMU0UgIyBydW4gY3Jvc3MtdmFsaWRhdGlvbiBvbiB0aGUgZ2JtIGJhc2VsaW5lCnNhbXBsZS5yZXdlaWdodCA8LSBUUlVFICMgcnVuIHNhbXBsZSByZXdlaWdodGluZyBpbiBtb2RlbCB0cmFpbmluZwpLIDwtIDUgICMgbnVtYmVyIG9mIENWIGZvbGRzCnJ1bi5mZWF0dXJlLnRyYWluIDwtIFRSVUUgIyBwcm9jZXNzIGZlYXR1cmVzIGZvciB0cmFpbmluZyBzZXQKcnVuLnRlc3QgPC0gVFJVRSAjIHJ1biBldmFsdWF0aW9uIG9uIGFuIGluZGVwZW5kZW50IHRlc3Qgc2V0CnJ1bi5mZWF0dXJlLnRlc3QgPC0gVFJVRSAjIHByb2Nlc3MgZmVhdHVyZXMgZm9yIHRlc3Qgc2V0CnJ1bi5jdi5zdm0gPC0gRkFMU0UKcnVuLnRlc3Quc3ZtIDwtIFRSVUUKCmBgYAoKVXNpbmcgY3Jvc3MtdmFsaWRhdGlvbiBvciBpbmRlcGVuZGVudCB0ZXN0IHNldCBldmFsdWF0aW9uLCB3ZSBjb21wYXJlIHRoZSBwZXJmb3JtYW5jZSBvZiBtb2RlbHMgd2l0aCBkaWZmZXJlbnQgc3BlY2lmaWNhdGlvbnMuIEluIHRoaXMgU3RhcnRlciBDb2RlLCB3ZSB0dW5lIHBhcmFtZXRlciBsYW1iZGEgKHRoZSBhbW91bnQgb2Ygc2hyaW5rYWdlKSBmb3IgbG9naXN0aWMgcmVncmVzc2lvbiB3aXRoIExBU1NPIHBlbmFsdHkuCgpgYGB7ciBtb2RlbF9zZXR1cH0KbG1iZCA9IGMoMWUtMywgNWUtMywgMWUtMiwgNWUtMiwgMWUtMSkKbW9kZWxfbGFiZWxzID0gcGFzdGUoIkxBU1NPIFBlbmFsdHkgd2l0aCBsYW1iZGEgPSIsIGxtYmQpCgojZ2JtIHBhcmFtZXRlcnMgdHVuaW5nOgpuLnRyZWVzID0gYygxMCw1MCwxMDAsMjAwKQpzaHJpbmthZ2UgPSBjKDAuMDEsMC4wNSwwLjEsMC4xNSkKCiNzdm0gcGFyYW1ldGVycyB0dW5pbmc6CmNvc3QgPSBjKDAuMDAwMDAwMSwwLjAwMDAwMSwwLjAwMDAxLDAuMDAwMSwwLjAwMSwwLjAxLDEpCm1vZGVsX2xhYmVsc19zdm0gPSBwYXN0ZSgiU1ZNIHdpdGggY29zdCA9IiwgY29zdCkKCiN4Z2Jvb3N0IHBhcmFtZXRlcnMgdHVuaW5nCnBhcmFtcyA8LSBsaXN0KGJvb3N0ZXIgPSAiZ2J0cmVlIiwgb2JqZWN0aXZlID0gImJpbmFyeTpsb2dpc3RpYyIsIAogICAgICAgICAgICAgICAgIGV0YT0wLjMsIGdhbW1hPTAsIG1heF9kZXB0aD02LCBtaW5fY2hpbGRfd2VpZ2h0PTEsIAogICAgICAgICAgICAgICAgIHN1YnNhbXBsZT0xLCBjb2xzYW1wbGVfYnl0cmVlPTEpCgojcmFuZG9tIGZvcmVzdCBwYXJhbWV0ZXJzIHR1bmluZwpudHJlZXMgPC0gMTI4IAojYWNjb3JkaW5nIHRvIGEgcGFwZXIgYnkgVGhhaXMgTWF5dW1pIE9zaGlybywgUGVkcm8gU2FudG9ybyBQZXJleiwgYW5kIEpvcyDMgWUgQXVndXN0byBCYXJhbmF1c2thLAojIHRoZSBBVUMgZ2FpbiBmb3IgaW5jcmVhc2luZyBudW1iZXIgb2YgdHJlZXMgaXMgbWluaW1hbCBhZnRlciAxMjgsIAojIGFmdGVyIG9ic2VydmluZyBhIG11bHRpdHVkZSBvZiB0aGVpciBkYXRhc2V0cwoKCmBgYAoKIyMjIFN0ZXAgMjogaW1wb3J0IGRhdGEgYW5kIHRyYWluLXRlc3Qgc3BsaXQgCmBgYHtyfQojdHJhaW4tdGVzdCBzcGxpdAppbmZvIDwtIHJlYWQuY3N2KHRyYWluX2xhYmVsX3BhdGgpCm4gPC0gbnJvdyhpbmZvKQpuX3RyYWluIDwtIHJvdW5kKG4qKDQvNSksIDApCnRyYWluX2lkeCA8LSBzYW1wbGUoaW5mbyRJbmRleCwgbl90cmFpbiwgcmVwbGFjZSA9IEYpCnRlc3RfaWR4IDwtIHNldGRpZmYoaW5mbyRJbmRleCwgdHJhaW5faWR4KQpgYGAKCklmIHlvdSBjaG9vc2UgdG8gZXh0cmFjdCBmZWF0dXJlcyBmcm9tIGltYWdlcywgc3VjaCBhcyB1c2luZyBHYWJvciBmaWx0ZXIsIFIgbWVtb3J5IHdpbGwgZXhoYXVzdCBhbGwgaW1hZ2VzIGFyZSByZWFkIHRvZ2V0aGVyLiBUaGUgc29sdXRpb24gaXMgdG8gcmVwZWF0IHJlYWRpbmcgYSBzbWFsbGVyIGJhdGNoKGUuZyAxMDApIGFuZCBwcm9jZXNzIHRoZW0uIApgYGB7cn0Kbl9maWxlcyA8LSBsZW5ndGgobGlzdC5maWxlcyh0cmFpbl9pbWFnZV9kaXIpKQoKaW1hZ2VfbGlzdCA8LSBsaXN0KCkKZm9yKGkgaW4gMToxMDApewogICBpbWFnZV9saXN0W1tpXV0gPC0gcmVhZEltYWdlKHBhc3RlMCh0cmFpbl9pbWFnZV9kaXIsIHNwcmludGYoIiUwNGQiLCBpKSwgIi5qcGciKSkKfQpgYGAKCkZpZHVjaWFsIHBvaW50cyBhcmUgc3RvcmVkIGluIG1hdGxhYiBmb3JtYXQuIEluIHRoaXMgc3RlcCwgd2UgcmVhZCB0aGVtIGFuZCBzdG9yZSB0aGVtIGluIGEgbGlzdC4KYGBge3IgcmVhZCBmaWR1Y2lhbCBwb2ludHN9CiNmdW5jdGlvbiB0byByZWFkIGZpZHVjaWFsIHBvaW50cwojaW5wdXQ6IGluZGV4CiNvdXRwdXQ6IG1hdHJpeCBvZiBmaWR1Y2lhbCBwb2ludHMgY29ycmVzcG9uZGluZyB0byB0aGUgaW5kZXgKcmVhZE1hdC5tYXRyaXggPC0gZnVuY3Rpb24oaW5kZXgpewogICAgIHJldHVybihyb3VuZChyZWFkTWF0KHBhc3RlMCh0cmFpbl9wdF9kaXIsIHNwcmludGYoIiUwNGQiLCBpbmRleCksICIubWF0IikpW1sxXV0sMCkpCn0KCiNsb2FkIGZpZHVjaWFsIHBvaW50cwpmaWR1Y2lhbF9wdF9saXN0IDwtIGxhcHBseSgxOm5fZmlsZXMsIHJlYWRNYXQubWF0cml4KQpzYXZlKGZpZHVjaWFsX3B0X2xpc3QsIGZpbGU9Ii4uL291dHB1dC9maWR1Y2lhbF9wdF9saXN0LlJEYXRhIikKYGBgCgojIyMgU3RlcCAzOiBjb25zdHJ1Y3QgZmVhdHVyZXMgYW5kIHJlc3BvbnNlcwoKKyBUaGUgZm9sbG93IHBsb3RzIHNob3cgaG93IHBhaXJ3aXNlIGRpc3RhbmNlIGJldHdlZW4gZmlkdWNpYWwgcG9pbnRzIGNhbiB3b3JrIGFzIGZlYXR1cmUgZm9yIGZhY2lhbCBlbW90aW9uIHJlY29nbml0aW9uLgoKICArIEluIHRoZSBmaXJzdCBjb2x1bW4sIDc4IGZpZHVjaWFscyBwb2ludHMgb2YgZWFjaCBlbW90aW9uIGFyZSBtYXJrZWQgaW4gb3JkZXIuIAogICsgSW4gdGhlIHNlY29uZCBjb2x1bW4gZGlzdHJpYnV0aW9ucyBvZiB2ZXJ0aWNhbCBkaXN0YW5jZSBiZXR3ZWVuIHJpZ2h0IHB1cGlsKDEpIGFuZCAgcmlnaHQgYnJvdyBwZWFrKDIxKSBhcmUgc2hvd24gaW4gIGhpc3RvZ3JhbXMuIEZvciBleGFtcGxlLCB0aGUgZGlzdGFuY2Ugb2YgYW4gYW5ncnkgZmFjZSB0ZW5kcyB0byBiZSBzaG9ydGVyIHRoYW4gdGhhdCBvZiBhIHN1cnByaXNlZCBmYWNlLgogICsgVGhlIHRoaXJkIGNvbHVtbiBpcyB0aGUgZGlzdHJpYnV0aW9ucyBvZiB2ZXJ0aWNhbCBkaXN0YW5jZXMgYmV0d2VlbiByaWdodCBtb3V0aCBjb3JuZXIoNTApCmFuZCB0aGUgbWlkcG9pbnQgb2YgdGhlIHVwcGVyIGxpcCg1MikuICBGb3IgZXhhbXBsZSwgdGhlIGRpc3RhbmNlIG9mIGFuIGhhcHB5IGZhY2UgdGVuZHMgdG8gYmUgc2hvcnRlciB0aGFuIHRoYXQgb2YgYSBzYWQgZmFjZS4KCiFbRmlndXJlMV0oLi4vZmlncy9mZWF0dXJlX3Zpc3VhbGl6YXRpb24uanBnKQoKYGZlYXR1cmUuUmAgc2hvdWxkIGJlIHRoZSB3cmFwcGVyIGZvciBhbGwgeW91ciBmZWF0dXJlIGVuZ2luZWVyaW5nIGZ1bmN0aW9ucyBhbmQgb3B0aW9ucy4gVGhlIGZ1bmN0aW9uIGBmZWF0dXJlKCApYCBzaG91bGQgaGF2ZSBvcHRpb25zIHRoYXQgY29ycmVzcG9uZCB0byBkaWZmZXJlbnQgc2NlbmFyaW9zIGZvciB5b3VyIHByb2plY3QgYW5kIHByb2R1Y2VzIGFuIFIgb2JqZWN0IHRoYXQgY29udGFpbnMgZmVhdHVyZXMgYW5kIHJlc3BvbnNlcyB0aGF0IGFyZSByZXF1aXJlZCBieSBhbGwgdGhlIG1vZGVscyB5b3UgYXJlIGdvaW5nIHRvIGV2YWx1YXRlIGxhdGVyLiAKICAKICArIGBmZWF0dXJlLlJgCiAgKyBJbnB1dDogbGlzdCBvZiBpbWFnZXMgb3IgZmlkdWNpYWwgcG9pbnQKICArIE91dHB1dDogYW4gUkRhdGEgZmlsZSB0aGF0IGNvbnRhaW5zIGV4dHJhY3RlZCBmZWF0dXJlcyBhbmQgY29ycmVzcG9uZGluZyByZXNwb25zZXMKCmBgYHtyIGZlYXR1cmV9CnNvdXJjZSgiLi4vbGliL2ZlYXR1cmUuUiIpCnRtX2ZlYXR1cmVfdHJhaW4gPC0gTkEKaWYocnVuLmZlYXR1cmUudHJhaW4pewogIHRtX2ZlYXR1cmVfdHJhaW4gPC0gc3lzdGVtLnRpbWUoZGF0X3RyYWluIDwtIGZlYXR1cmUoZmlkdWNpYWxfcHRfbGlzdCwgdHJhaW5faWR4KSkKICBzYXZlKGRhdF90cmFpbiwgZmlsZT0iLi4vb3V0cHV0L2ZlYXR1cmVfdHJhaW4uUkRhdGEiKQp9ZWxzZXsKICBsb2FkKGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3RyYWluLlJEYXRhIikKfQoKdG1fZmVhdHVyZV90ZXN0IDwtIE5BCmlmKHJ1bi5mZWF0dXJlLnRlc3QpewogIHRtX2ZlYXR1cmVfdGVzdCA8LSBzeXN0ZW0udGltZShkYXRfdGVzdCA8LSBmZWF0dXJlKGZpZHVjaWFsX3B0X2xpc3QsIHRlc3RfaWR4KSkKICBzYXZlKGRhdF90ZXN0LCBmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV90ZXN0LlJEYXRhIikKfWVsc2V7CiAgbG9hZChmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV90ZXN0LlJEYXRhIikKfQoKCmBgYAoKIyMjIFN0ZXAgNDogVHJhaW4gYSBjbGFzc2lmaWNhdGlvbiBtb2RlbCB3aXRoIHRyYWluaW5nIGZlYXR1cmVzIGFuZCByZXNwb25zZXMKQ2FsbCB0aGUgdHJhaW4gbW9kZWwgYW5kIHRlc3QgbW9kZWwgZnJvbSBsaWJyYXJ5LiAKCmB0cmFpbi5SYCBhbmQgYHRlc3QuUmAgc2hvdWxkIGJlIHdyYXBwZXJzIGZvciBhbGwgeW91ciBtb2RlbCB0cmFpbmluZyBzdGVwcyBhbmQgeW91ciBjbGFzc2lmaWNhdGlvbi9wcmVkaWN0aW9uIHN0ZXBzLiAKCisgYHRyYWluLlJgCiAgKyBJbnB1dDogYSBkYXRhIGZyYW1lIGNvbnRhaW5pbmcgZmVhdHVyZXMgYW5kIGxhYmVscyBhbmQgYSBwYXJhbWV0ZXIgbGlzdC4KICArIE91dHB1dDphIHRyYWluZWQgbW9kZWwKKyBgdGVzdC5SYAogICsgSW5wdXQ6IHRoZSBmaXR0ZWQgY2xhc3NpZmljYXRpb24gbW9kZWwgdXNpbmcgdHJhaW5pbmcgZGF0YSBhbmQgcHJvY2Vzc2VkIGZlYXR1cmVzIGZyb20gdGVzdGluZyBpbWFnZXMgCiAgKyBJbnB1dDogYW4gUiBvYmplY3QgdGhhdCBjb250YWlucyBhIHRyYWluZWQgY2xhc3NpZmllci4KICArIE91dHB1dDogdHJhaW5pbmcgbW9kZWwgc3BlY2lmaWNhdGlvbgoKKyBJbiB0aGlzIFN0YXJ0ZXIgQ29kZSwgd2UgdXNlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gd2l0aCBMQVNTTyBwZW5hbHR5IHRvIGRvIGNsYXNzaWZpY2F0aW9uLiAKCmBgYHtyIGxvYWRsaWJ9CnNvdXJjZSgiLi4vbGliL3RyYWluLlIiKSAKc291cmNlKCIuLi9saWIvdGVzdC5SIikKCnNvdXJjZSgiLi4vbGliL3RyYWluX2dibS5SIikKc291cmNlKCIuLi9saWIvdGVzdF9nYm0uUiIpCgpzb3VyY2UoIi4uL2xpYi90cmFpbl9TVk0uUiIpIApzb3VyY2UoIi4uL2xpYi90ZXN0X1NWTS5SIikKCnNvdXJjZSgiLi4vbGliL2ZpdF90cmFpbl94Z2Jvb3N0LlIiKQpzb3VyY2UoIi4uL2xpYi9maXRfdHJhaW5fcmFuZG9tZm9yZXN0LlIiKQpgYGAKCiMjIyMgTW9kZWwgc2VsZWN0aW9uIHdpdGggY3Jvc3MtdmFsaWRhdGlvbgoqIERvIG1vZGVsIHNlbGVjdGlvbiBieSBjaG9vc2luZyBhbW9uZyBkaWZmZXJlbnQgdmFsdWVzIG9mIHRyYWluaW5nIG1vZGVsIHBhcmFtZXRlcnMuCgoqKkJhc2VsaW5lIE1vZGVsKioKKiBCYXNlbGluZS9HQk0KYGBge3IgcnVuY3Z9CmZlYXR1cmVfdHJhaW4gPSBhcy5tYXRyaXgoZGF0X3RyYWluWywgLTYwMDddKQpsYWJlbF90cmFpbiA9IGFzLmludGVnZXIoZGF0X3RyYWluJGxhYmVsKSAKCnNvdXJjZSgiLi4vbGliL2Nyb3NzX3ZhbGlkYXRpb24uUiIpCnNvdXJjZSgiLi4vbGliL2Nyb3NzX3ZhbGlkYXRpb25fU1ZNLlIiKQpzb3VyY2UoIi4uL2xpYi9jdl9nYm0uUiIpCgppZihydW4uY3YuYmFzZWxpbmUpeyAgCiAgCiAgbWVhbl9lcnJvcl9jdiA8LSBtYXRyaXgoMCwgbnJvdyA9IGxlbmd0aChuLnRyZWVzKSwgbmNvbCA9IGxlbmd0aChzaHJpbmthZ2UpKQogIHNkX2Vycm9yX2N2IDwtIG1hdHJpeCgwLCBucm93ID0gbGVuZ3RoKG4udHJlZXMpLCBuY29sID0gbGVuZ3RoKHNocmlua2FnZSkpCiAgbWVhbl9hdWNfY3YgPC0gbWF0cml4KDAsIG5yb3cgPSBsZW5ndGgobi50cmVlcyksIG5jb2wgPSBsZW5ndGgoc2hyaW5rYWdlKSkKICBzZF9hdWNfY3YgPC0gbWF0cml4KDAsIG5yb3cgPSBsZW5ndGgobi50cmVlcyksIG5jb2wgPSBsZW5ndGgoc2hyaW5rYWdlKSkKCiAgZm9yKGkgaW4gMTpsZW5ndGgobi50cmVlcykpewogICAgY2F0KCJuLnRyZWVzID0iLCBuLnRyZWVzW2ldLCJcbiIpCiAgZm9yKGsgaW4gMTpsZW5ndGgoc2hyaW5rYWdlKSl7CiAgICBjYXQoInNocmlua2FnZSA9Iiwgc2hyaW5rYWdlW2tdLCJcbiIpCiAgICAKcmVzX2N2X2dibSA8LSBjdl9nYm0oZmVhdHVyZXMgPSBmZWF0dXJlX3RyYWluLCBsYWJlbHMgPSBsYWJlbF90cmFpbiwgSywgIG4udHJlZXMgPSBuLnRyZWVzW2ldLHNocmlua2FnZSA9IHNocmlua2FnZVtrXSxyZXdlaWdodCA9IHNhbXBsZS5yZXdlaWdodCkKICAgICAgCiAgICBtZWFuX2Vycm9yX2N2W2ksa108LXJlc19jdl9nYm1bMV0KICAgICBzZF9lcnJvcl9jdltpLGtdPC1yZXNfY3ZfZ2JtWzJdCiAgICAgIG1lYW5fYXVjX2N2W2ksa108LXJlc19jdl9nYm1bM10KICAgICAgIHNkX2F1Y19jdltpLGtdPC1yZXNfY3ZfZ2JtWzRdCiAgICAgIAogICAgc2F2ZShtZWFuX2Vycm9yX2N2LCBmaWxlPSIuLi9vdXRwdXQvbWVhbl9lcnJvcl9jdi5SRGF0YSIpCiAgICBzYXZlKHNkX2Vycm9yX2N2LCBmaWxlPSIuLi9vdXRwdXQvc2RfZXJyb3JfY3YuUkRhdGEiKQogICAgc2F2ZShtZWFuX2F1Y19jdiwgZmlsZT0iLi4vb3V0cHV0L21lYW5fYXVjX2N2LlJEYXRhIikKICAgIHNhdmUoc2RfYXVjX2N2LCBmaWxlPSIuLi9vdXRwdXQvc2RfYXVjX2N2LlJEYXRhIikgCiAgICB9fQogfSBlbHNlewogIGxvYWQoIi4uL291dHB1dC9tZWFuX2Vycm9yX2N2LlJEYXRhIikKICBsb2FkKCIuLi9vdXRwdXQvc2RfZXJyb3JfY3YuUkRhdGEiKSAgIAogIGxvYWQoIi4uL291dHB1dC9tZWFuX2F1Y19jdi5SRGF0YSIpCiAgbG9hZCgiLi4vb3V0cHV0L3NkX2F1Y19jdi5SRGF0YSIpCiAgICB9CgppZihydW4uY3YpewogIHJlc19jdiA8LSBtYXRyaXgoMCwgbnJvdyA9IGxlbmd0aChsbWJkKSwgbmNvbCA9IDQpCiAgZm9yKGkgaW4gMTpsZW5ndGgobG1iZCkpewogICAgY2F0KCJsYW1iZGEgPSAiLCBsbWJkW2ldLCAiXG4iKQogICAgcmVzX2N2W2ksXSA8LSBjdi5mdW5jdGlvbihmZWF0dXJlcyA9IGZlYXR1cmVfdHJhaW4sIGxhYmVscyA9IGxhYmVsX3RyYWluLCBLLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbCA9IGxtYmRbaV0sIHJld2VpZ2h0ID0gc2FtcGxlLnJld2VpZ2h0KQogIHNhdmUocmVzX2N2LCBmaWxlPSIuLi9vdXRwdXQvcmVzX2N2LlJEYXRhIikKICB9Cn1lbHNlewogIGxvYWQoIi4uL291dHB1dC9yZXNfY3YuUkRhdGEiKQp9CmBgYAoKKiBTVk0KYGBge3Igc3ZtfQppZihydW4uY3Yuc3ZtKXsKICByZXNfY3Zfc3ZtIDwtIG1hdHJpeCgwLCBucm93ID0gbGVuZ3RoKGNvc3QpLCBuY29sID0gNCkKICBmb3IoaSBpbiAxOmxlbmd0aChjb3N0KSl7CiAgICBjYXQoImNvc3Q9ICIsIGNvc3RbaV0sICJcbiIpCiAgICByZXNfY3Zfc3ZtW2ksXSA8LSBzdm1fY3YoZmVhdHVyZXMgPSBmZWF0dXJlX3RyYWluLCBsYWJlbHMgPSBsYWJlbF90cmFpbiwgSywgY29zdD1jb3N0W2ldLCByZXdlaWdodCA9IHNhbXBsZS5yZXdlaWdodCkKICBzYXZlKHJlc19jdl9zdm0sIGZpbGU9Ii4uL291dHB1dC9yZXNfY3Zfc3ZtLlJEYXRhIikKICB9Cn1lbHNlewogIGxvYWQoIi4uL291dHB1dC9yZXNfY3Zfc3ZtLlJEYXRhIikKfQpgYGAKCioqSW1wcm92ZWQgTW9kZWwqKgoqIFhHQm9vc3QgCmBgYHtyIFBhcmFtZXRlciBPcHRpbWl6YXRpb24gVGhyb3VnaCBDcm9zcyBWYWxpZGF0aW9uIGZvciBYR0Jvb3N0fQoKc291cmNlKCIuLi9saWIveGdib29zdF9jdi5SIikKZmVhdHVyZV90cmFpbiA9IGFzLm1hdHJpeChkYXRfdHJhaW5bLCAtNjAwN10pCmxhYmVsX3RyYWluID0gYXMuaW50ZWdlcihkYXRfdHJhaW4kbGFiZWwpIApsYWJlbF90cmFpbl94Z2IgPC0gbGFiZWxfdHJhaW4KbGFiZWxfdHJhaW5feGdiW2xhYmVsX3RyYWluX3hnYiA9PSAyXSA8LSAwCgpzZXRfcm91bmRzICA8LSA1MApLIDwtIDUKCm5ld19wYXJhbXMgPC0gY3ZfeGdib29zdChwYXJhbXMsIGZlYXR1cmVfdHJhaW4sIGxhYmVsX3RyYWluX3hnYiwgc2V0X3JvdW5kcywgSykKCnNhdmUobmV3X3BhcmFtcywgZmlsZT0iLi4vb3V0cHV0L3Jlc19jdl94Z2Jvb3N0LlJEYXRhIikKCmBgYCAKCmBgYHtyIGN2IHJlc3VsdCBkZn0KbGlicmFyeSh0aWR5cikKCmRmX21lYW5fZXJyb3I9ZGF0YS5mcmFtZShtZWFuX2Vycm9yX2N2KSU+JQpzZXROYW1lcyhzaHJpbmthZ2UpJT4lCm11dGF0ZShuLnRyZWVzPW4udHJlZXMpJT4lCmdhdGhlcihzaHJpbmthZ2UsbWVhbl9lcnJvcixgMC4wMWA6YDAuMTVgKQoKZGZfc2RfZXJyb3I9ZGF0YS5mcmFtZShzZF9lcnJvcl9jdiklPiUKc2V0TmFtZXMoc2hyaW5rYWdlKSU+JQptdXRhdGUobi50cmVlcz1uLnRyZWVzKSU+JQpnYXRoZXIoc2hyaW5rYWdlLHNkX2Vycm9yLGAwLjAxYDpgMC4xNWApCgpkZl9tZWFuX2F1Yz1kYXRhLmZyYW1lKG1lYW5fYXVjX2N2KSU+JQpzZXROYW1lcyhzaHJpbmthZ2UpJT4lCm11dGF0ZShuLnRyZWVzPW4udHJlZXMpJT4lCmdhdGhlcihzaHJpbmthZ2UsbWVhbl9hdWMsYDAuMDFgOmAwLjE1YCkKCmRmX3NkX2F1Yz1kYXRhLmZyYW1lKHNkX2F1Y19jdiklPiUKc2V0TmFtZXMoc2hyaW5rYWdlKSU+JQptdXRhdGUobi50cmVlcz1uLnRyZWVzKSU+JQpnYXRoZXIoc2hyaW5rYWdlLHNkX2F1YyxgMC4wMWA6YDAuMTVgKQoKcmVzX2N2X2dibSA8LSBkZl9tZWFuX2Vycm9yJT4lbXV0YXRlKHNkX2Vycm9yPWRmX3NkX2Vycm9yJHNkX2Vycm9yLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWVhbl9hdWM9ZGZfbWVhbl9hdWMkbWVhbl9hdWMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZF9hdWM9ZGZfc2RfYXVjJHNkX2F1YykKc2F2ZShyZXNfY3ZfZ2JtLGZpbGUgPSAiLi4vb3V0cHV0L3Jlc19jdl9nYm0uUkRhdGEiKQpgYGAKClZpc3VhbGl6ZSBjcm9zcy12YWxpZGF0aW9uIHJlc3VsdHMuIApgYGB7ciBjdl92aXN9CmxvYWQoIi4uL291dHB1dC9yZXNfY3ZfZ2JtLlJEYXRhIikKCgppZihydW4uY3YuYmFzZWxpbmUpewogIHAxIDwtIHJlc19jdl9nYm0gJT4lIAogICAgZ2dwbG90KGFlcyh4ID0gbi50cmVlcywgeSA9IG1lYW5fZXJyb3IsCiAgICAgICAgICAgICAgIHltaW4gPSBtZWFuX2Vycm9yIC0gc2RfZXJyb3IsIHltYXggPSBtZWFuX2Vycm9yICArc2RfZXJyb3IpKSArIAogICAgZ2VvbV9jcm9zc2JhcigpICsKICAgIGZhY2V0X3dyYXAofnNocmlua2FnZSkgKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxKSkKICAKICBwMiA8LSByZXNfY3ZfZ2JtICU+JSAKICAgIGdncGxvdChhZXMoeCA9IG4udHJlZXMsIHkgPSBtZWFuX2F1YywKICAgICAgICAgICAgICAgeW1pbiA9IG1lYW5fYXVjIC0gc2RfYXVjLCB5bWF4ID0gbWVhbl9hdWMgKyBzZF9hdWMpKSArICAgICBmYWNldF93cmFwKH5zaHJpbmthZ2UpICsKICAgIGdlb21fY3Jvc3NiYXIoKSArCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKQogIAogIHByaW50KHAxKQogIHByaW50KHAyKQp9CgpyZXNfY3Zfc3ZtIDwtIGFzLmRhdGEuZnJhbWUocmVzX2N2X3N2bSkgCmNvbG5hbWVzKHJlc19jdl9zdm0pIDwtIGMoIm1lYW5fZXJyb3IiLCAic2RfZXJyb3IiLCAibWVhbl9BVUMiLCAic2RfQVVDIikKcmVzX2N2X3N2bSRrID0gYXMuZmFjdG9yKGNvc3QpCnJ1bi5jdi5zdm0gPC0gVFJVRQppZihydW4uY3Yuc3ZtKXsKICBwMSA8LSByZXNfY3Zfc3ZtICU+JSAKICAgIGdncGxvdChhZXMoeCA9IGFzLmZhY3Rvcihjb3N0KSwgeSA9IG1lYW5fZXJyb3IsCiAgICAgICAgICAgICAgIHltaW4gPSBtZWFuX2Vycm9yIC0gc2RfZXJyb3IsIHltYXggPSBtZWFuX2Vycm9yICsgc2RfZXJyb3IpKSArIAogICAgZ2VvbV9jcm9zc2JhcigpICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpCiAgCiAgcDIgPC0gcmVzX2N2X3N2bSAlPiUgCiAgICBnZ3Bsb3QoYWVzKHggPSBhcy5mYWN0b3IoY29zdCksIHkgPSBtZWFuX0FVQywKICAgICAgICAgICAgICAgeW1pbiA9IG1lYW5fQVVDIC0gc2RfQVVDLCB5bWF4ID0gbWVhbl9BVUMgKyBzZF9BVUMpKSArIAogICAgZ2VvbV9jcm9zc2JhcigpICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpCiAgCiAgcHJpbnQocDEpCiAgcHJpbnQocDIpCn0KYGBgCgoKKiBDaG9vc2UgdGhlICJiZXN0IiBwYXJhbWV0ZXIgdmFsdWUgZm9yIGJhc2VsaW5lIG1vZGVsCmBgYHtyIGJlc3RfbW9kZWx9CmJlc3Rfbi50cmVlcyA9IGFzLm51bWVyaWMocmVzX2N2X2dibVt3aGljaC5taW4ocmVzX2N2X2dibSRtZWFuX2Vycm9yKSwxXSkKYmVzdF9zaHJpbmthZ2UgPSBhcy5udW1lcmljKHJlc19jdl9nYm1bd2hpY2gubWluKHJlc19jdl9nYm0kbWVhbl9lcnJvciksMl0pCgpiZXN0X2Nvc3QgPC0gY29zdFt3aGljaC5taW4ocmVzX2N2X3N2bSRtZWFuX2Vycm9yKV0KYGBgCgoqIENob29zZSAiYmVzdCIgbnVtYmVyIG9mIHRyZWVzIGFuZCBtdHJ5IGZvciByYW5kb20gZm9yZXN0IG1vZGVsCmBgYHtyIGJlc3RfbW9kZWxfcmZ9CnNvdXJjZSgiLi4vbGliL3JmX3BhcmFtX2Nob2ljZS5SIikKZmVhdHVyZV90cmFpbiA9IGFzLm1hdHJpeChkYXRfdHJhaW5bLCAtNjAwN10pCmxhYmVsX3RyYWluID0gYXMuaW50ZWdlcihkYXRfdHJhaW4kbGFiZWwpIAppbml0X210cnkgPC0gc3FydChuY29sKGRhdF90cmFpbikpCgpyZl9vcHRfdHJlZSA8LSBwYXJhbV9jaG9pY2VfcmYoZmVhdHVyZV90cmFpbiA9IGZlYXR1cmVfdHJhaW4sIGxhYmVsX3RyYWluID0gIGxhYmVsX3RyYWluLCBtdHJ5ID0gaW5pdF9tdHJ5LCBudHJlZSA9IG50cmVlcykKCnNhdmUocmZfb3B0X3RyZWUsIGZpbGUgPSAiLi4vb3V0cHV0L3Jlc19vb2JfcmYuUkRhdGEiKQpgYGAKCgojIyMjIFRyYWluIGRpZmZlcmVudCBtb2RlbHMKKiBUcmFpbiB0aGUgYmFzZWxpbmUgbW9kZWwgd2l0aCB0aGUgZW50aXJlIHRyYWluaW5nIHNldCB1c2luZyB0aGUgc2VsZWN0ZWQgbW9kZWwgKG1vZGVsIHBhcmFtZXRlcikgdmlhIGNyb3NzLXZhbGlkYXRpb24uCmBgYHtyIGZpbmFsX3RyYWluX2Jhc2VsaW5lfQojIHRyYWluaW5nIHdlaWdodHMKd2VpZ2h0X3RyYWluIDwtIHJlcChOQSwgbGVuZ3RoKGxhYmVsX3RyYWluKSkKZm9yICh2IGluIHVuaXF1ZShsYWJlbF90cmFpbikpewogIHdlaWdodF90cmFpbltsYWJlbF90cmFpbiA9PSB2XSA9IDAuNSAqIGxlbmd0aChsYWJlbF90cmFpbikgLyBsZW5ndGgobGFiZWxfdHJhaW5bbGFiZWxfdHJhaW4gPT0gdl0pCn0KaWYgKHNhbXBsZS5yZXdlaWdodCl7CiAgdG1fdHJhaW5fYmFzZWxpbmUgPC0gc3lzdGVtLnRpbWUoZml0X3RyYWluX2Jhc2VsaW5lIDwtIHRyYWluX2dibShmZWF0dXJlX3RyYWluLCBsYWJlbF90cmFpbiwgdyA9IHdlaWdodF90cmFpbixiZXN0X24udHJlZXMsIGJlc3Rfc2hyaW5rYWdlKSkKfSBlbHNlIHsKICB0bV90cmFpbl9iYXNlbGluZSA8LSBzeXN0ZW0udGltZShmaXRfdHJhaW5fYmFzZWxpbmUgPC0gdHJhaW5fZ2JtKGZlYXR1cmVfdHJhaW4sIGxhYmVsX3RyYWluLCB3ID0gTlVMTCwgYmVzdF9uLnRyZWVzLCBiZXN0X3Nocmlua2FnZSkpCn0Kc2F2ZShmaXRfdHJhaW5fYmFzZWxpbmUsIGZpbGU9Ii4uL291dHB1dC9maXRfdHJhaW5fYmFzZWxpbmUuUkRhdGEiKQpgYGAKCiogVHJhaW4gdGhlIFNWTSBtb2RlbCB3aXRoIHRoZSBlbnRpcmUgdHJhaW5pbmcgc2V0IHVzaW5nIHRoZSBzZWxlY3RlZCBtb2RlbCAobW9kZWwgcGFyYW1ldGVyKSB2aWEgY3Jvc3MtdmFsaWRhdGlvbi4KYGBge3IgZmluYWxfdHJhaW5fc3ZtfQp3ZWlnaHRfdHJhaW4gPC0gcmVwKE5BLCBsZW5ndGgobGFiZWxfdHJhaW4pKQpmb3IgKHYgaW4gdW5pcXVlKGxhYmVsX3RyYWluKSl7CiAgd2VpZ2h0X3RyYWluW2xhYmVsX3RyYWluID09IHZdID0gMC41ICogbGVuZ3RoKGxhYmVsX3RyYWluKSAvIGxlbmd0aChsYWJlbF90cmFpbltsYWJlbF90cmFpbiA9PSB2XSkKfQppZiAoc2FtcGxlLnJld2VpZ2h0KXsKICB0bV90cmFpbl9zdm0gPC0gc3lzdGVtLnRpbWUoZml0X3RyYWluX3N2bSA8LXN2bV90cmFpbihmZWF0dXJlX3RyYWluLCBsYWJlbF90cmFpbiwgdyA9IHdlaWdodF90cmFpbiwgYmVzdF9jb3N0KSkKfSBlbHNlIHsKICB0bV90cmFpbl9zdm0gPC0gc3lzdGVtLnRpbWUoZml0X3RyYWluX3N2bSA8LXN2bV90cmFpbihmZWF0dXJlX3RyYWluLCBsYWJlbF90cmFpbiwgdyA9IE5VTEwsIGJlc3RfY29zdCkpCn0Kc2F2ZShmaXRfdHJhaW5fc3ZtLCBmaWxlPSIuLi9vdXRwdXQvZml0X3RyYWluX3N2bS5SRGF0YSIpCmBgYAoKCiogVHJhaW4gdGhlIFhHQm9vc3QgbW9kZWwgd2l0aCBvcHRpbWFsIHBhcmFtZXRlcnMKCmBgYHtyIGZpbmFsX3RyYWluX3hnYn0KCnhnYl90cmFpbl90aW1lIDwtIHN5c3RlbS50aW1lKGZpdF90cmFpbl94Z2IgPC0geGdib29zdF90cmFpbihmZWF0dXJlcyA9IGZlYXR1cmVfdHJhaW4sIGxhYmVscyA9IGxhYmVsX3RyYWluX3hnYiwgcGFyYW1zID0gbmV3X3BhcmFtcywgcm91bmRzID0gIHNldF9yb3VuZHMpKQoKc2F2ZShmaXRfdHJhaW5feGdiLCBmaWxlPSIuLi9vdXRwdXQvZml0X3RyYWluX3hnYi5SRGF0YSIpCgpgYGAKCiogVHJhaW4gdGhlIFJhbmRvbSBGb3Jlc3QgbW9kZWwgd2l0aCBvcHRpbWFsIHBhcmFtZXRlcnMKCmBgYHtyIGZpbmFsX3RyYWluX3JmfQpsb2FkKGZpbGUgPSAiLi4vb3V0cHV0L3Jlc19vb2JfcmYuUkRhdGEiKQpyZl90cmFpbl90aW1lIDwtIHN5c3RlbS50aW1lKGZpdF90cmFpbl9yYW5kb21mb3Jlc3QgPC0gcmZfdHJhaW4oZmVhdHVyZV90cmFpbiA9IGZlYXR1cmVfdHJhaW4sIGxhYmVsX3RyYWluID0gbGFiZWxfdHJhaW4sIG10cnkgPSByZl9vcHRfdHJlZSRtdHJ5LCBudHJlZSA9IHJmX29wdF90cmVlJG50cmVlKSkKc2F2ZShmaXRfdHJhaW5fcmFuZG9tZm9yZXN0LCBmaWxlPSIuLi9vdXRwdXQvZml0X3RyYWluX3JmLlJEYXRhIikKYGBgCgoqIHRyYWluIFBDQSArIExEQQpgYGB7ciBQQ0F9CgojUENBIGZvciB0cmFpbmluZyBmZWF0dXJlcwpzb3VyY2UoIi4uL2xpYi90cmFpbl9QQ0EuUiIpCgojIG1ha2UgZGF0X3RyYWluIGEgbnVtZXJpYyBkYXRhIGZyYW1lCmRhdF90cmFpbi5uZXcgPC0gbWF0cml4KDAsIG5jb2wgPSBuY29sKGRhdF90cmFpbikgLSAxLCBucm93ID0gbnJvdyhkYXRfdHJhaW4pKQpmb3IgKGkgaW4gMToobmNvbChkYXRfdHJhaW4pIC0gMSkpIHsKZGF0X3RyYWluLm5ld1ssaV0gPC0gYXMubnVtZXJpYyhkYXRfdHJhaW5bW2ldXSkKfQpkYXRfdHJhaW4ubmV3IDwtIGFzLmRhdGEuZnJhbWUoZGF0X3RyYWluLm5ldykKCiNQQ0EgZm9yIHRyYWluaW5nIGZlYXR1cmVzCnRtX3RyYWluX3BjYSA8LSBzeXN0ZW0udGltZShmaXRfdHJhaW5fcGNhIDwtIHRyYWluX3BjYShkYXRfdHJhaW4ubmV3KSkKCnNhdmUoZml0X3RyYWluX3BjYSwgZmlsZT0iLi4vb3V0cHV0L3BjYV90cmFpbi5SRGF0YSIpCgojIGRldGVybWluZSB0aGUgaW1wb3J0YW50IHByaW5jaXBsZSBjb21wb25lbnRzCnNjcmVlcGxvdChmaXRfdHJhaW5fcGNhLCB0eXBlID0gImwiKQoKIyBUaGUgcHJvcG9ydGlvbiBvZiB2YXJpYW5jZSBmb3IgZmlyc3QgMzAwIFBDcwpzdW0oKGZpdF90cmFpbl9wY2Ekc2RldlsxOjMwMF0pXjIpIC8gc3VtKChmaXRfdHJhaW5fcGNhJHNkZXYpXjIpCgpgYGAKVGhlcmVmb3JlLCB3ZSBjYW4gY2hvb3NlIHRoZSBmaXJzdCAzMDAgcHJpbmNpcGxlIGNvbXBvbmVudHMgYmVjYXVzZSB0aGV5IGV4cGxhaW4gbW9zdCBvZiB0aGUgdG90YWwgdmFyaWFuY2UsIHdoaWNoIGlzIGFyb3VuZCA5OS45JS4KCmBgYHtyIFBDQSB0cmFpbn0KIyBnZXQgdGhlIGZlYXR1cmVzIG9mIHByaW5jaXBsZSBjb21wb25lbnRzIHdpdGggZW1vdGlvbiBpbmRleApkYXRfdHJhaW5fcGNhIDwtIGRhdGEuZnJhbWUoZml0X3RyYWluX3BjYSR4WywxOjMwMF0sIGVtb3Rpb25faWR4ID0gZGF0X3RyYWluWyw2MDA3XSkKYGBgCgpVc2UgdHJhaW5lZCBQQ0EgbW9kZWwgdG8gdGVzdCBkYXRhCmBgYHtyIFBDQSB0ZXN0fQpzb3VyY2UoIi4uL2xpYi90ZXN0X1BDQS5SIikKZGF0X3Rlc3QubmV3IDwtIGRhdF90ZXN0CmNvbG5hbWVzKGRhdF90ZXN0Lm5ldykgPC0gYyhjb2xuYW1lcyhkYXRfdHJhaW4ubmV3KSwgImVtb3Rpb25faWR4IikKCnRtX3Rlc3RfcGNhIDwtIHN5c3RlbS50aW1lKGRhdF90ZXN0Lm5ldyA8LSB0ZXN0X3BjYShmaXRfdHJhaW5fcGNhLCBkYXRfdGVzdC5uZXcpKQoKI2ZlYXR1cmVzIG9mIHRlc3RpbmcgcHJpbmNpcGxlIGNvbXBvbmVudHMgd2l0aCB0aGUgZW1vdGlvbiBpbmRleApkYXRfdGVzdF9wY2EgPC0gZGF0YS5mcmFtZShkYXRfdGVzdC5uZXdbLDE6MzAwXSwgZW1vdGlvbl9pZHggPSBkYXRfdGVzdFssNjAwN10pCgpzYXZlKGRhdF90cmFpbl9wY2EsIGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3RyYWluX3BjYS5SRGF0YSIpCnNhdmUoZGF0X3Rlc3RfcGNhLCBmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV90ZXN0X3BjYS5SRGF0YSIpCmBgYAoKQXBwbHkgTERBIG1vZGVsCgpgYGB7ciBMREEgdHJhaW59CiNsb2FkKCIuLi9vdXRwdXQvZmVhdHVyZV90cmFpbl9wY2EuUkRhdGEiKQoKI3RyYWluIExEQSBtb2RlbApzb3VyY2UoIi4uL2xpYi90cmFpbl9MREEuUiIpCnRtX3RyYWluX2xkYSA8LSBzeXN0ZW0udGltZShmaXRfdHJhaW5fbGRhIDwtIHRyYWluX2xkYShkYXRfdHJhaW5fcGNhKSkKc2F2ZShmaXRfdHJhaW5fbGRhLCBmaWxlPSIuLi9vdXRwdXQvTERBX3RyYWluLlJEYXRhIikKCiN0cmFpbmluZyBhY2N1cmFjeSBpbiBMREEgbW9kZWwKc291cmNlKCIuLi9saWIvdGVzdF9MREEuUiIpCnByZWRfdHJhaW5fbGRhIDwtIHRlc3RfbGRhKGZpdF90cmFpbl9sZGEsIGRhdF90cmFpbl9wY2EpCmFjY3VfdHJhaW5fbGRhIDwtIG1lYW4ocHJlZF90cmFpbl9sZGEgPT0gZGF0X3RyYWluX3BjYSRlbW90aW9uX2lkeCkKCiNjYXQoIlRoZSB0cmFpbmlnIGFjY3VyYWN5IG9mIG1vZGVsIExEQSIsICJpcyIsIGFjY3VfdHJhaW5fbGRhKjEwMCwgIiUuXG4iKQojY2F0KCJUaW1lIGZvciB0cmFpbmluZyBtb2RlbCBMREEgPSAiLCB0bV90cmFpbl9sZGFbMV0sICJzIFxuIikKYGBgCgoKIyMjIFN0ZXAgNTogUnVuIHRlc3Qgb24gdGVzdCBpbWFnZXMKCipCYXNlbGluZSBtb2RlbApgYGB7ciB0ZXN0IGdibX0KdG1fdGVzdF9iYXNlbGluZSA9IE5BCmZlYXR1cmVfdGVzdCA8LSBhcy5tYXRyaXgoZGF0X3Rlc3RbLCAtNjAwN10pCmlmKHJ1bi50ZXN0KXsKICBsb2FkKGZpbGU9Ii4uL291dHB1dC9maXRfdHJhaW5fYmFzZWxpbmUuUkRhdGEiKQogIHRtX3Rlc3RfYmFzZWxpbmUgPC0gc3lzdGVtLnRpbWUoe2xhYmVsX3ByZWRfYmFzZWxpbmUgPC0gYXMuaW50ZWdlcih0ZXN0X2dibShmaXRfdHJhaW5fYmFzZWxpbmUsZmVhdHVyZV90ZXN0LGJlc3Rfbi50cmVlcywgYmVzdF9zaHJpbmthZ2UsIHByZWQudHlwZSA9ICdsaW5rJykpOyAKICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9iX3ByZWRfYmFzZWxpbmUgPC0gdGVzdF9nYm0oZml0X3RyYWluX2Jhc2VsaW5lLCBmZWF0dXJlX3Rlc3QsYmVzdF9uLnRyZWVzLCBiZXN0X3Nocmlua2FnZSwgcHJlZC50eXBlID0gJ3Jlc3BvbnNlJyl9KQp9CmBgYAoKKlNWTSBtb2RlbApgYGB7ciB0ZXN0IHN2bX0KdG1fdGVzdCA9IE5BCmZlYXR1cmVfdGVzdCA8LSBhcy5tYXRyaXgoZGF0X3Rlc3RbLCAtNjAwN10pCmlmKHJ1bi50ZXN0LnN2bSl7CiAgbG9hZChmaWxlPSIuLi9vdXRwdXQvZml0X3RyYWluX3N2bS5SRGF0YSIpCiAgdG1fdGVzdF9zdm0gPC0gc3lzdGVtLnRpbWUoewogICAgbGFiZWxfcHJlZF9zdm0gPC0gYXMuaW50ZWdlcihzdm1fdGVzdChmaXRfdHJhaW5fc3ZtLCBmZWF0dXJlX3Rlc3QsIHByZWQudHlwZSA9ICdjbGFzcycpKTsgCiAgICBwcm9iX3ByZWRfc3ZtIDwtIHN2bV90ZXN0KGZpdF90cmFpbl9zdm0sIGZlYXR1cmVfdGVzdCwgcHJlZC50eXBlID0gJ3Jlc3BvbnNlJyl9KQp9CmBgYAoKKlhHQm9vc3QKYGBge3IgdGVzdCB4Z2Jvb3N0fQp0bV90ZXN0X3hnYiA9IE5BCmZlYXR1cmVfdGVzdCA8LSBhcy5tYXRyaXgoZGF0X3Rlc3RbLCAtNjAwN10pCmlmKHJ1bi50ZXN0KXsKICBsb2FkKGZpbGU9Ii4uL291dHB1dC9maXRfdHJhaW5feGdiLlJEYXRhIikKICB0bV90ZXN0X3hnYiA8LSBzeXN0ZW0udGltZSh7bGFiZWxfcHJlZF94Z2IgPC0gcHJlZGljdChmaXRfdHJhaW5feGdiLCBmZWF0dXJlX3Rlc3QsIHByZWQudHlwZSA9ICdjbGFzcycpOwogICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsX3ByZWRfeGdiW2xhYmVsX3ByZWRfeGdiID49IDAuNV0gPC0gMTsKICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbF9wcmVkX3hnYltsYWJlbF9wcmVkX3hnYiA8IDAuNV0gPC0gIDA7CiAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvYl9wcmVkX3hnYiA8LSBwcmVkaWN0KGZpdF90cmFpbl94Z2IsIGZlYXR1cmVfdGVzdCwgcHJlZC50eXBlID0gJ3Jlc3BvbnNlJyl9KQp9CmBgYAoKKlJhbmRvbSBGb3Jlc3QKYGBge3IgdGVzdCByYW5kb20gZm9yZXN0IH0KdG1fdGVzdF9yZiA9IE5BCmZlYXR1cmVfdGVzdCA8LSBhcy5tYXRyaXgoZGF0X3Rlc3RbLCAtNjAwN10pCmlmKHJ1bi50ZXN0KXsKICBsb2FkKGZpbGU9Ii4uL291dHB1dC9maXRfdHJhaW5fcmYuUkRhdGEiKQogIHRtX3Rlc3RfcmYgPC0gc3lzdGVtLnRpbWUoe2xhYmVsX3ByZWRfcmYgPC0gcHJlZGljdChmaXRfdHJhaW5fcmFuZG9tZm9yZXN0LCBmZWF0dXJlX3Rlc3QsIHByZWQudHlwZSA9ICdjbGFzcycpOwogICAgICAgICAgICAgICAgICAgICAgICAgIHByb2JfcHJlZF9yZiA8LSBwcmVkaWN0KGZpdF90cmFpbl9yYW5kb21mb3Jlc3QsIGZlYXR1cmVfdGVzdCwgdHlwZSA9ICdwcm9iJyl9KQp9IAoKYGBgCgoqTERBCmBgYHtyfQpzb3VyY2UoIi4uL2xpYi90ZXN0X0xEQS5SIikKbG9hZChmaWxlID0gIi4uL291dHB1dC9mZWF0dXJlX3Rlc3RfcGNhLlJEYXRhIikKbG9hZChmaWxlPSIuLi9vdXRwdXQvTERBX3RyYWluLlJEYXRhIikKdG1fdGVzdF9sZGEgPC0gc3lzdGVtLnRpbWUocHJlZF9sZGEgPC0gdGVzdF9sZGEoZml0X3RyYWluX2xkYSwgZGF0X3Rlc3RfcGNhKSkKCmBgYAoKCiMjIyBFdmFsdWF0aW9uCmBgYHtyIGJhc2VsaW5lfQojIyByZXdlaWdodCB0aGUgdGVzdCBkYXRhIHRvIHJlcHJlc2VudCBhIGJhbGFuY2VkIGxhYmVsIGRpc3RyaWJ1dGlvbgpsYWJlbF90ZXN0IDwtIGFzLmludGVnZXIoZGF0X3Rlc3QkbGFiZWwpCndlaWdodF90ZXN0IDwtIHJlcChOQSwgbGVuZ3RoKGxhYmVsX3Rlc3QpKQpmb3IgKHYgaW4gdW5pcXVlKGxhYmVsX3Rlc3QpKXsKICB3ZWlnaHRfdGVzdFtsYWJlbF90ZXN0ID09IHZdID0gMC41ICogbGVuZ3RoKGxhYmVsX3Rlc3QpIC8gbGVuZ3RoKGxhYmVsX3Rlc3RbbGFiZWxfdGVzdCA9PSB2XSkKfQoKYWNjdV9iYXNlbGluZSA8LSBtZWFuKGxhYmVsX3ByZWRfYmFzZWxpbmUgPT0gbGFiZWxfdGVzdCkKdHByLmZwci5iYXNlbGluZSA8LSBXZWlnaHRlZFJPQyhwcm9iX3ByZWRfYmFzZWxpbmUsIGxhYmVsX3Rlc3QsIHdlaWdodF90ZXN0KQphdWNfYmFzZWxpbmUgPC0gV2VpZ2h0ZWRBVUModHByLmZwci5iYXNlbGluZSkKCgpjYXQoIlRoZSBhY2N1cmFjeSBvZiBtb2RlbCBHQk06IHdpdGggbi50cmVlcz0iLGJlc3Rfbi50cmVlcywiYW5kIHNocmlua2FnZSA9IiwgYmVzdF9zaHJpbmthZ2UsICJpcyIsIGFjY3VfYmFzZWxpbmUqMTAwLCAiJS5cbiIpCmNhdCgiVGhlIEFVQyBvZiBtb2RlbCBHQk06IHdpdGggbi50cmVlcz0iLCBiZXN0X24udHJlZXMsImFuZCBzaHJpbmthZ2UgPSIsIGJlc3Rfc2hyaW5rYWdlLCAiaXMiLCBhdWNfYmFzZWxpbmUsICIuXG4iKQoKCmBgYAoKYGBge3IgZXZhbHV0YXRpb25fU1ZNfQpsYWJlbF90ZXN0IDwtIGFzLmludGVnZXIoZGF0X3Rlc3QkbGFiZWwpCndlaWdodF90ZXN0IDwtIHJlcChOQSwgbGVuZ3RoKGxhYmVsX3Rlc3QpKQpmb3IgKHYgaW4gdW5pcXVlKGxhYmVsX3Rlc3QpKXsKICB3ZWlnaHRfdGVzdFtsYWJlbF90ZXN0ID09IHZdID0gMC41ICogbGVuZ3RoKGxhYmVsX3Rlc3QpIC8gbGVuZ3RoKGxhYmVsX3Rlc3RbbGFiZWxfdGVzdCA9PSB2XSkKfQoKYWNjdV9zdm0gPC0gbWVhbihsYWJlbF9wcmVkX3N2bSA9PSBsYWJlbF90ZXN0KQp0cHIuZnByLnN2bSA8LSBXZWlnaHRlZFJPQyhwcm9iX3ByZWRfc3ZtLCBsYWJlbF90ZXN0LCB3ZWlnaHRfdGVzdCkKYXVjX3N2bSA8LSBXZWlnaHRlZEFVQyh0cHIuZnByLnN2bSkKY2F0KCJUaGUgYWNjdXJhY3kgb2YgbW9kZWw6IiwgbW9kZWxfbGFiZWxzX3N2bVt3aGljaC5taW4ocmVzX2N2X3N2bSRtZWFuX2Vycm9yKV0sICJpcyIsIGFjY3Vfc3ZtKjEwMCwgIiUuXG4iKQpjYXQoIlRoZSBBVUMgb2YgbW9kZWw6IiwgbW9kZWxfbGFiZWxzX3N2bVt3aGljaC5taW4ocmVzX2N2X3N2bSRtZWFuX2Vycm9yKV0sICJpcyIsIGF1Y19zdm0sICIuXG4iKQoKCmBgYAoKYGBge3IgZXZhbHVhdGlvbl9YR0J9CmxhYmVsX3Rlc3QgPC0gYXMuaW50ZWdlcihkYXRfdGVzdCRsYWJlbCkKbGFiZWxfdGVzdF94Z2IgPC0gbGFiZWxfdGVzdApsYWJlbF90ZXN0X3hnYltsYWJlbF90ZXN0X3hnYj09Ml0gPSAwCmFjY3VfeGdiIDwtIG1lYW4oKGxhYmVsX3ByZWRfeGdiID09IGxhYmVsX3Rlc3RfeGdiKSkKdHByLmZwcl94Z2IgPC0gV2VpZ2h0ZWRST0MocHJvYl9wcmVkX3hnYiwgbGFiZWxfdGVzdF94Z2IpCmF1Y194Z2IgPC0gV2VpZ2h0ZWRBVUModHByLmZwcl94Z2IpCgoKY2F0KCJUaGUgYWNjdXJhY3kgb2YgdGhlIFhHQm9vc3QgbW9kZWw6IiwgImlzIiwgYWNjdV94Z2IqMTAwLCAiJS5cbiIpCmNhdCgiVGhlIEFVQyBvZiB0aGUgWEdCb29zdCBtb2RlbDoiLCAiaXMiLCBhdWNfeGdiLCAiLlxuIikKCmBgYAoKYGBge3IgZXZhbHVhdGlvbl9yZn0KbGFiZWxfdGVzdCA8LSBhcy5pbnRlZ2VyKGRhdF90ZXN0JGxhYmVsKQphY2N1X3JmIDwtIG1lYW4obGFiZWxfcHJlZF9yZiA9PSBsYWJlbF90ZXN0KQp0cHIuZnByLnJmIDwtIFdlaWdodGVkUk9DKHByb2JfcHJlZF9yZlssMl0sIGxhYmVsX3Rlc3QpCmF1Y19yZiA8LSBXZWlnaHRlZEFVQyh0cHIuZnByLnJmKQoKCmNhdCgiVGhlIGFjY3VyYWN5IG9mIHRoZSBSYW5kb20gRm9yZXN0IG1vZGVsOiIsICJpcyIsIGFjY3VfcmYqMTAwLCAiJS5cbiIpCmNhdCgiVGhlIEFVQyBvZiB0aGUgUmFuZG9tIEZvcmVzdCBtb2RlbDoiLCAiaXMiLCBhdWNfcmYsICIuXG4iKQpgYGAKCmBgYHtyIGV2YWx1YXRpb25fUENBK0xEQX0KYWNjdV9sZGEgPC0gbWVhbihkYXRfdGVzdF9wY2EkZW1vdGlvbl9pZHggPT0gcHJlZF9sZGEpCmxhYmVsX2xkYSA8LSBhcy5udW1lcmljKGRhdF90ZXN0X3BjYSRlbW90aW9uX2lkeCkKdHByLmZwci5sZGEgPC0gV2VpZ2h0ZWRST0MoYXMubnVtZXJpYyhwcmVkX2xkYSksIGxhYmVsX2xkYSkKYXVjX2xkYSA8LSBXZWlnaHRlZEFVQyh0cHIuZnByLmxkYSkKCmNhdCgiVGhlIGFjY3VyYWN5IG9mIHRoZSBQQ0ErTERBIG1vZGVsOiIsICJpcyIsIGFjY3VfbGRhKjEwMCwgIiUuXG4iKQpjYXQoIlRoZSBBVUMgb2YgdGhlIFBDQStMREEgbW9kZWw6IiwgImlzIiwgYXVjX2xkYSwgIi5cbiIpCmBgYAoKIyMjIFN1bW1hcml6ZSBSdW5uaW5nIFRpbWUKUHJlZGljdGlvbiBwZXJmb3JtYW5jZSBtYXR0ZXJzLCBzbyBkb2VzIHRoZSBydW5uaW5nIHRpbWVzIGZvciBjb25zdHJ1Y3RpbmcgZmVhdHVyZXMgYW5kIGZvciB0cmFpbmluZyB0aGUgbW9kZWwsIGVzcGVjaWFsbHkgd2hlbiB0aGUgY29tcHV0YXRpb24gcmVzb3VyY2UgaXMgbGltaXRlZC4gCmBgYHtyIHJ1bm5pbmdfdGltZV9iYXNlbGluZX0KY2F0KCJUaW1lIGZvciBjb25zdHJ1Y3RpbmcgdHJhaW5pbmcgZmVhdHVyZXM9IiwgdG1fZmVhdHVyZV90cmFpblsxXSwgInMgXG4iKQpjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyB0ZXN0aW5nIGZlYXR1cmVzPSIsIHRtX2ZlYXR1cmVfdGVzdFsxXSwgInMgXG4iKQpjYXQoIlRpbWUgZm9yIHRyYWluaW5nIG1vZGVsPSIsIHRtX3RyYWluX2Jhc2VsaW5lWzFdLCAicyBcbiIpIApjYXQoIlRpbWUgZm9yIHRlc3RpbmcgbW9kZWw9IiwgdG1fdGVzdF9iYXNlbGluZVsxXSwgInMgXG4iKQpgYGAKCgpgYGB7ciBydW5uaW5nX3RpbWVfU1ZNfQpjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyB0cmFpbmluZyBmZWF0dXJlcz0iLCB0bV9mZWF0dXJlX3RyYWluWzFdLCAicyBcbiIpCmNhdCgiVGltZSBmb3IgY29uc3RydWN0aW5nIHRlc3RpbmcgZmVhdHVyZXM9IiwgdG1fZmVhdHVyZV90ZXN0WzFdLCAicyBcbiIpCmNhdCgiVGltZSBmb3IgdHJhaW5pbmcgbW9kZWw9IiwgdG1fdHJhaW5fc3ZtWzFdLCAicyBcbiIpIApjYXQoIlRpbWUgZm9yIHRlc3RpbmcgbW9kZWw9IiwgdG1fdGVzdF9zdm1bMV0sICJzIFxuIikKYGBgCgoKYGBge3IgcnVubmluZ190aW1lX1hHQn0KY2F0KCJUaW1lIGZvciBjb25zdHJ1Y3RpbmcgdHJhaW5pbmcgZmVhdHVyZXM9IiwgdG1fZmVhdHVyZV90cmFpblsxXSwgInMgXG4iKQpjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyB0ZXN0aW5nIGZlYXR1cmVzPSIsIHRtX2ZlYXR1cmVfdGVzdFsxXSwgInMgXG4iKQpjYXQoIlRpbWUgZm9yIHRyYWluaW5nIG1vZGVsPSIsIHhnYl90cmFpbl90aW1lWzFdLCAicyBcbiIpIApjYXQoIlRpbWUgZm9yIHRlc3RpbmcgbW9kZWw9IiwgdG1fdGVzdF94Z2JbMV0sICJzIFxuIikKCmBgYAoKYGBge3IgcnVubmluZ190aW1lX3JmfQpjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyB0cmFpbmluZyBmZWF0dXJlcz0iLCB0bV9mZWF0dXJlX3RyYWluWzFdLCAicyBcbiIpCmNhdCgiVGltZSBmb3IgY29uc3RydWN0aW5nIHRlc3RpbmcgZmVhdHVyZXM9IiwgdG1fZmVhdHVyZV90ZXN0WzFdLCAicyBcbiIpCmNhdCgiVGltZSBmb3IgdHJhaW5pbmcgcmFuZG9tIGZvcmVzdCBtb2RlbD0iLCByZl90cmFpbl90aW1lWzFdLCAicyBcbiIpCmNhdCgiVGltZSBmb3IgdGVzdGluZyByYW5kb20gZm9yZXN0IG1vZGVsPSIsIHRtX3Rlc3RfcmZbMV0sICJzIFxuIikKYGBgCgoKYGBge3IgcnVubmluZ190aW1lX1BDQStMREF9CmNhdCgiVGltZSBmb3IgY29uc3RydWN0aW5nIHRyYWluaW5nIGZlYXR1cmVzPSIsIHRtX2ZlYXR1cmVfdHJhaW5bMV0sICJzIFxuIikKY2F0KCJUaW1lIGZvciBjb25zdHJ1Y3RpbmcgdGVzdGluZyBmZWF0dXJlcz0iLCB0bV9mZWF0dXJlX3Rlc3RbMV0sICJzIFxuIikKCmNhdCgiVGltZSBmb3IgdHJhaW5pbmcgUENBID0iLCB0bV90cmFpbl9wY2FbMV0sICJzIFxuIikKY2F0KCJUaW1lIGZvciB0ZXN0aW5nIFBDQSA9IiwgdG1fdGVzdF9wY2FbMV0sICJzIFxuIikKY2F0KCJUaW1lIGZvciB0cmFpbmluZyBtb2RlbCBMREEgPSAiLCB0bV90cmFpbl9sZGFbMV0sICJzIFxuIikKY2F0KCJUaW1lIGZvciB0ZXN0aW5nIG1vZGVsIExEQSA9ICIsdG1fdGVzdF9sZGFbMV0sICJzIFxuIikKYGBgCiMjI1JlZmVyZW5jZQotIER1LCBTLiwgVGFvLCBZLiwgJiBNYXJ0aW5leiwgQS4gTS4gKDIwMTQpLiBDb21wb3VuZCBmYWNpYWwgZXhwcmVzc2lvbnMgb2YgZW1vdGlvbi4gUHJvY2VlZGluZ3Mgb2YgdGhlIE5hdGlvbmFsIEFjYWRlbXkgb2YgU2NpZW5jZXMsIDExMSgxNSksIEUxNDU0LUUxNDYyLgoKCgoKCgoKCgoKCgoK